package com.yohann.boot.config.aspect;

import com.yohann.boot.common.annotation.LogRecordable;
import com.yohann.boot.entity.LogRecord;
import com.yohann.boot.service.LogRecordService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.*;

/**
 * @author Yeung Wah
 * @since 2022/1/5
 */
@Aspect
@Component
@ConditionalOnBean(LogRecordService.class)
public class LogRecordAspect {

    @Autowired
    private List<LogRecordService> logRecordServices;

    @Pointcut("@annotation(com.yohann.boot.common.annotation.LogRecordable)")
    public void logRecord() {
    }

    /**
     * 环绕通知
     */
    @Around("logRecord()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        LogRecord logRecord = new LogRecord();
        Date startTime = new Date();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        //目标方法
        Object target = pjp.getTarget();
        String className = target.getClass().getName();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        LogRecordable annotation = method.getAnnotation(LogRecordable.class);

        //请求参数
        Object[] args = pjp.getArgs();
        String[] parameterNames = signature.getParameterNames();
        Map<String, Object> paramMap = buildParamMap(args, parameterNames);

        //记录日志
        logRecord.setId(uuid);
        logRecord.setStartTime(startTime);
        logRecord.setArguments(paramMap);
        logRecord.setMethod(className + "#" + methodName);
        logRecord.setDescription(annotation.value());

        try {
            // action
            Object response = pjp.proceed();
            Date endTime = new Date();
            logRecord.setEndTime(endTime);
            logRecord.setResponse(response);
            return response;
        } catch (Throwable throwable) {
            Date endTime = new Date();
            logRecord.setEndTime(endTime);
            logRecord.setThrowable(throwable);
            throw throwable;
        } finally {
            Duration between = Duration.between(logRecord.getStartTime().toInstant(), logRecord.getEndTime().toInstant());
            logRecord.setTimeConsuming(between.toMillis());
            logRecordServices.forEach(service -> service.record(logRecord));
        }
    }

    private Map<String, Object> buildParamMap(Object[] args, String[] parameterNames) {
        Map<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof HttpServletRequest
                    || args[i] instanceof HttpServletResponse
                    || args[i] instanceof HttpSession
                    || args[i] instanceof Cookie
                    || args[i] instanceof MultipartFile
                    || args[i] instanceof MultipartFile[]) {
                continue;
            }
            paramMap.put(parameterNames[i], args[i]);
        }

        return paramMap;
    }
}